Allow build script feedback to the crate compiled
authorAlex Crichton <alex@alexcrichton.com>
Thu, 9 Apr 2015 21:35:48 +0000 (14:35 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Tue, 14 Apr 2015 17:15:31 +0000 (10:15 -0700)
This commit enables the build script for a crate to provide feedback to the
crate itself about how it should be built. This is done through the `--cfg`
flags of the compiler, and each build script is now allowed to print `rustc-cfg`
directives to inform Cargo about what `--cfg` flags it should pass.

All `--cfg` flags are local to the current crate and are not propagated outwards
to transitive dependencies. The primary use-case that this feature is targeting
is compile-time feature detection for applications like C bindings or C
libraries where the version being targeted may change over time.

Closes #1478

src/cargo/ops/cargo_compile.rs
src/cargo/ops/cargo_rustc/custom_build.rs
src/cargo/ops/cargo_rustc/mod.rs
src/doc/build-script.md
tests/test_cargo_compile_custom_build.rs

index 0ef49cdde258cecd64fac0282ba93ffe74019060..96fd5f4a10bd6b2e251c4079028dcfed6126227b 100644 (file)
@@ -379,6 +379,7 @@ fn scrape_target_config(config: &Config, triple: &str)
         let mut output = BuildOutput {
             library_paths: Vec::new(),
             library_links: Vec::new(),
+            cfgs: Vec::new(),
             metadata: Vec::new(),
         };
         let key = format!("{}.{}", key, lib_name);
@@ -406,6 +407,8 @@ fn scrape_target_config(config: &Config, triple: &str)
                         output.library_paths.extend(a.into_iter().map(|v| {
                             PathBuf::from(&v.0)
                         }));
+                    } else if k == "rustc-cfg" {
+                        output.cfgs.extend(a.into_iter().map(|v| v.0));
                     } else {
                         try!(config.expected("string", &k,
                                              ConfigValue::List(a, p)));
index c64d3e49ec2d47a458652221b77b2a98cefb935a..1f7d427cba2bacf3a79f2d9ca3c62959e2772bf2 100644 (file)
@@ -21,6 +21,8 @@ pub struct BuildOutput {
     pub library_paths: Vec<PathBuf>,
     /// Names and link kinds of libraries, suitable for the `-l` flag
     pub library_links: Vec<String>,
+    /// Various `--cfg` flags to pass to the compiler
+    pub cfgs: Vec<String>,
     /// Metadata to pass to the immediate dependencies
     pub metadata: Vec<(String, String)>,
 }
@@ -252,6 +254,7 @@ impl BuildOutput {
     pub fn parse(input: &str, pkg_name: &str) -> CargoResult<BuildOutput> {
         let mut library_paths = Vec::new();
         let mut library_links = Vec::new();
+        let mut cfgs = Vec::new();
         let mut metadata = Vec::new();
         let whence = format!("build script of `{}`", pkg_name);
 
@@ -277,24 +280,25 @@ impl BuildOutput {
                                               whence, line)))
             };
 
-            if key == "rustc-flags" {
-                let (libs, links) = try!(
-                    BuildOutput::parse_rustc_flags(value, &whence)
-                );
-                library_links.extend(links.into_iter());
-                library_paths.extend(libs.into_iter());
-            } else if key == "rustc-link-lib" {
-                library_links.push(value.to_string());
-            } else if key == "rustc-link-search" {
-                library_paths.push(PathBuf::from(value));
-            } else {
-                metadata.push((key.to_string(), value.to_string()))
+            match key {
+                "rustc-flags" => {
+                    let (libs, links) = try!(
+                        BuildOutput::parse_rustc_flags(value, &whence)
+                    );
+                    library_links.extend(links.into_iter());
+                    library_paths.extend(libs.into_iter());
+                }
+                "rustc-link-lib" => library_links.push(value.to_string()),
+                "rustc-link-search" => library_paths.push(PathBuf::from(value)),
+                "rustc-cfg" => cfgs.push(value.to_string()),
+                _ => metadata.push((key.to_string(), value.to_string())),
             }
         }
 
         Ok(BuildOutput {
             library_paths: library_paths,
             library_links: library_links,
+            cfgs: cfgs,
             metadata: metadata,
         })
     }
@@ -317,8 +321,8 @@ impl BuildOutput {
             }
             let value = match flags_iter.next() {
                 Some(v) => v,
-                None => return Err(human(format!("Flag in rustc-flags has no value \
-                                                  in {}: `{}`",
+                None => return Err(human(format!("Flag in rustc-flags has no \
+                                                  value in {}: `{}`",
                                                   whence, value)))
             };
             match flag {
index 5617135d9cacb362cc90db07b742b4dd9c723ee6..ac9f02a96b9a2559d0ecfbad46dca5e4b85b31bb 100644 (file)
@@ -436,9 +436,14 @@ fn rustc(package: &Package, target: &Target, profile: &Profile,
             for path in output.library_paths.iter() {
                 rustc.arg("-L").arg(path);
             }
-            if pass_l_flag && id == *current_id {
-                for name in output.library_links.iter() {
-                    rustc.arg("-l").arg(name);
+            if id == *current_id {
+                for cfg in &output.cfgs {
+                    rustc.arg("--cfg").arg(cfg);
+                }
+                if pass_l_flag {
+                    for name in output.library_links.iter() {
+                        rustc.arg("-l").arg(name);
+                    }
                 }
             }
         }
index 61ff5e44dd344dc3e1cac93ec819eeb32521d21a..31ff5ea4315b5fb35373207a66b90883509e29e0 100644 (file)
@@ -79,18 +79,22 @@ Example output:
 ```notrust
 cargo:rustc-link-lib=static=foo
 cargo:rustc-link-search=native=/path/to/foo
+cargo:rustc-cfg=foo
 cargo:root=/path/to/foo
 cargo:libdir=/path/to/foo/lib
 cargo:include=/path/to/foo/include
 ```
 
-The `rustc-link-lib` key indicates that Cargo should pass a `-l` option to
-rustc. Similarly, `rustc-link-search` indicates that Cargo should pass a `-L`
-option.
+There are a few special keys that Cargo recognizes, affecting how the crate this
+build script is for is built:
 
-The `rustc-flags` key is special and indicates the flags that Cargo will
-pass to Rustc. Currently only `-l` and `-L` are accepted. Using
-`rustc-link-lib` and `rustc-link-search` is more robust.
+* `rustc-link-lib` indicates that the specified value should be passed to the
+  compiler as a `-l` flag.
+* `rustc-link-search` indicates the specified value should be passed to the
+  compiler as a `-L` flag.
+* `rustc-cfg` indicates that the specified directive will be passed as a `--cfg`
+  flag to the compiler. This is often useful for performing compile-time
+  detection of various features.
 
 Any other element is a user-defined metadata that will be passed to
 dependencies. More information about this can be found in the [`links`][links]
index d5fc4057ab51d7467de799e1b6440fc0a1b56986..35a1963a791b84c80d83aa45fdd25bef27a1ec46 100644 (file)
@@ -1252,3 +1252,51 @@ test!(test_duplicate_deps {
 
     assert_that(p.cargo_process("build"), execs().with_status(0));
 });
+
+test!(cfg_feedback {
+    let build = project("builder")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "builder"
+            version = "0.0.1"
+            authors = []
+            build = "build.rs"
+        "#)
+        .file("src/main.rs", "
+            #[cfg(foo)]
+            fn main() {}
+        ")
+        .file("build.rs", r#"
+            fn main() {
+                println!("cargo:rustc-cfg=foo");
+            }
+        "#);
+    assert_that(build.cargo_process("build"),
+                execs().with_status(0));
+});
+
+test!(cfg_override {
+    let (_, target) = ::cargo::ops::rustc_version().unwrap();
+
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.5.0"
+            authors = []
+            links = "a"
+            build = "build.rs"
+        "#)
+        .file("src/main.rs", "
+            #[cfg(foo)]
+            fn main() {}
+        ")
+        .file("build.rs", "")
+        .file(".cargo/config", &format!(r#"
+            [target.{}.a]
+            rustc-cfg = ["foo"]
+        "#, target));
+
+    assert_that(p.cargo_process("build").arg("-v"),
+                execs().with_status(0));
+});